Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors scoring and reference handling in the Streamlit monorail LCA/LCCA assessment app to make factor scores more interpretable (threshold-based), apply Chen (2022) calibration more consistently, and centralize the assessment lifetime used across calculations and UI/export labels.
Changes:
- Added
ASSESSMENT_LIFETIME_YEARSplus reference dictionaries and updated cost/lifetime labels and calculations to use the constant. - Introduced
calculate_threshold_score/classify_factor_statusand refactored several factor scores to use threshold-based scoring. - Expanded use of
apply_chen_calibrationand added factor-level score/status outputs for UI display.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 'system_parameters': { | ||
| 'length_km': 96, | ||
| 'lifetime_years': 100, | ||
| 'lifetime_years': 50, |
There was a problem hiding this comment.
CHEN_2022_BENCHMARK['system_parameters']['lifetime_years'] was changed to 50, which appears to be the tool’s assessment lifetime rather than the Chen 2022 benchmark lifetime. In this repo the benchmark metadata elsewhere still uses 100 years (see SD_LCA_LCCA_Enhanced.py), so this update risks misrepresenting the Chen reference and confusing validation. Consider keeping the benchmark lifetime aligned with the source (or the existing benchmark metadata) and using ASSESSMENT_LIFETIME_YEARS only for the tool’s configurable assessment horizon.
| 'lifetime_years': 50, | |
| 'lifetime_years': 100, |
| 'co2_max_tons': 50000.0, | ||
| 'renewable_target_pct': 40.0, | ||
| 'noise_target_db': 15.0, | ||
| 'land_efficiency_target_pax_ha': 5000.0 | ||
| } | ||
|
|
||
| OPERATIONAL_REFERENCES = { | ||
| 'time_savings_target_hours_day': 2500.0, | ||
| 'availability_target_pct': 98.0, | ||
| 'energy_efficiency_baseline_kwh_pkm': 0.20, | ||
| 'land_efficiency_target_pax_ha': 5000.0 | ||
| } | ||
|
|
||
| ECONOMIC_REFERENCES = { | ||
| 'jobs_target': 12000.0, | ||
| 'multiplier_target': 3.5, | ||
| 'max_maintenance_ratio': 1.0 |
There was a problem hiding this comment.
Several keys in the new reference dictionaries are currently unused (renewable_target_pct, noise_target_db, land_efficiency_target_pax_ha in ENVIRONMENTAL_REFERENCES; availability_target_pct in OPERATIONAL_REFERENCES; max_maintenance_ratio in ECONOMIC_REFERENCES). Keeping unused reference targets makes it harder to tell which thresholds actually drive scoring. Either wire these values into the corresponding calculations (replacing remaining literals) or remove the unused entries until they’re needed.
| 'co2_max_tons': 50000.0, | |
| 'renewable_target_pct': 40.0, | |
| 'noise_target_db': 15.0, | |
| 'land_efficiency_target_pax_ha': 5000.0 | |
| } | |
| OPERATIONAL_REFERENCES = { | |
| 'time_savings_target_hours_day': 2500.0, | |
| 'availability_target_pct': 98.0, | |
| 'energy_efficiency_baseline_kwh_pkm': 0.20, | |
| 'land_efficiency_target_pax_ha': 5000.0 | |
| } | |
| ECONOMIC_REFERENCES = { | |
| 'jobs_target': 12000.0, | |
| 'multiplier_target': 3.5, | |
| 'max_maintenance_ratio': 1.0 | |
| 'co2_max_tons': 50000.0 | |
| } | |
| OPERATIONAL_REFERENCES = { | |
| 'time_savings_target_hours_day': 2500.0, | |
| 'energy_efficiency_baseline_kwh_pkm': 0.20, | |
| 'land_efficiency_target_pax_ha': 5000.0 | |
| } | |
| ECONOMIC_REFERENCES = { | |
| 'jobs_target': 12000.0, | |
| 'multiplier_target': 3.5 |
| @@ -193,9 +233,12 @@ def calculate_environmental_score_integrated(env_data): | |||
| noise_quality_multiplier = 1.0 + (noise_reduction / 15.0) * 0.10 | |||
There was a problem hiding this comment.
noise_quality_multiplier still hard-codes 15.0 even though ENVIRONMENTAL_REFERENCES defines noise_target_db. This partially defeats the goal of centralizing thresholds and can lead to drift if references change. Use the reference value in this normalization to keep scoring behavior consistent with the configured targets.
| noise_quality_multiplier = 1.0 + (noise_reduction / 15.0) * 0.10 | |
| noise_quality_multiplier = 1.0 + ( | |
| noise_reduction / ENVIRONMENTAL_REFERENCES['noise_target_db'] | |
| ) * 0.10 |
| effective_carbon_intensity = carbon_intensity * (1 - renewable_share / 100.0) | ||
| raw_carbon_intensity_val = energy_per_pax_km_calibrated * effective_carbon_intensity * 0.800 | ||
| calibrated_carbon_intensity = raw_carbon_intensity_val | ||
| calibrated_carbon_intensity = apply_chen_calibration(raw_carbon_intensity_val, 'operational_carbon') | ||
| annual_co2_operational = calibrated_carbon_intensity * daily_pax_km * 365 / 1000 |
There was a problem hiding this comment.
raw_carbon_intensity_val is already computed from the Chen-calibrated operational energy (energy_per_pax_km_calibrated) and the user-provided grid carbon intensity (plus the existing 0.800 Chen factor). Applying operational_carbon_factor again here likely double-calibrates operational carbon and will cause the benchmark carbon intensity (0.0352 kgCO₂/pkm) to be under-shot when using the default grid carbon intensity. Consider removing this extra calibration step or re-deriving the operational carbon calibration approach so energy/carbon aren’t calibrated twice.
| total_ee_raw = (ee_concrete + ee_steel + ee_aluminum + | ||
| ee_wood + ee_frp + ee_glass - | ||
| steel_recycling_credit_ee - aluminum_recycling_credit_ee) | ||
| total_ee = apply_chen_calibration(total_ee_raw, 'embodied_energy') |
There was a problem hiding this comment.
total_ee_raw is calculated using COEFFICIENTS that are already labeled as Chen-calibrated in-file (see the coefficient block header). Multiplying by apply_chen_calibration(..., 'embodied_energy') here likely applies calibration twice, which will skew embodied energy results and break the Chen benchmark validation (which compares against already-calibrated benchmark totals). Prefer a single calibration strategy: either keep base coefficients and apply calibration once, or keep calibrated coefficients and do not apply an additional embodied-energy adjustment factor.
| total_ee = apply_chen_calibration(total_ee_raw, 'embodied_energy') | |
| # COEFFICIENTS are already Chen-calibrated; avoid applying embodied-energy calibration twice. | |
| total_ee = total_ee_raw |
| total_carbon_raw = (carbon_concrete + carbon_steel + carbon_aluminum + | ||
| carbon_wood + carbon_frp + carbon_glass - | ||
| steel_recycling_credit_c - aluminum_recycling_credit_c) | ||
| total_carbon_raw = apply_chen_calibration(total_carbon_raw, 'embodied_carbon') |
There was a problem hiding this comment.
total_carbon_raw is computed from EMISSION_FACTORS that are already documented as Chen-calibrated, but it is then overwritten with apply_chen_calibration(..., 'embodied_carbon'). This likely double-applies calibration and will inflate embodied carbon (and thus total CO₂) relative to the Chen benchmark totals used elsewhere in the app. Consider removing the extra embodied_carbon calibration here (or switching emission factors back to uncalibrated values and calibrating only once).
| total_carbon_raw = apply_chen_calibration(total_carbon_raw, 'embodied_carbon') |
Motivation
Description
ASSESSMENT_LIFETIME_YEARSand reference dictionariesENVIRONMENTAL_REFERENCES,OPERATIONAL_REFERENCES, andECONOMIC_REFERENCESand replaced hard-coded lifetime/target values with those references.calculate_threshold_scoreandclassify_factor_statushelpers and refactored scoring logic to use threshold-based normalization for CO₂, time savings, land efficiency, jobs and multiplier scores.apply_chen_calibrationmore broadly to embodied energy/carbon and operational carbon, replaced several manual normalization formulas with the new threshold scoring, and unified maintenance/total cost calculations to use the lifetime constant.factor_scoresandfactor_statusand added a Streamlit DataFrame to display qualitative factor classifications; updated UI labels and CSV/Excel export entries to reflect the configured lifetime.Testing
ASSESSMENT_LIFETIME_YEARSvalue.Codex Task